// HttpClient.ts
import * as http from 'http';
import * as https from 'https';
import * as fs from 'fs';
import * as path from 'path';

export interface HttpResponse<T = unknown> {
  statusCode: number | undefined;
  headers: http.IncomingHttpHeaders;
  body: string;
  json?: T;
}

type H = Record<string, string | number>;

export class HttpClient {
  private readonly hostname: string;
  private readonly port: number;
  private readonly protocol: 'http:' | 'https:';
  private cookieJar: Record<string, string> = {}; // name -> value
  public token?: string;

  constructor(baseUrl: string) {
    const u = new URL(baseUrl);
    this.hostname = u.hostname;
    this.port = u.port ? Number(u.port) : (u.protocol === 'https:' ? 443 : 80);
    this.protocol = (u.protocol === 'https:' ? 'https:' : 'http:');
  }

  /** GET */
  public get<T = unknown>(pathname: string, headers: H = {}) {
    return this.request<T>({ method: 'GET', pathname, headers });
  }

  /** POST(JSON) */
  public postJSON<T = unknown>(pathname: string, data: unknown, headers: H = {}) {
    const body = JSON.stringify(data ?? {});
    return this.request<T>({
      method: 'POST',
      pathname,
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(body),
        ...headers,
      },
      body,
    });
  }

  /** multipart/form-data 文件上传 */
  public uploadFile<T = unknown>(
      pathname: string,
      filePath: string,
      fields: Record<string, string | number> = {},
      headers: H = {},
  ): Promise<HttpResponse<T>> {
    const boundary = '----WebKitFormBoundary' + Math.random().toString(16).slice(2);
    const fileName = path.basename(filePath);

    let prelude = '';
    for (const [k, v] of Object.entries(fields)) {
      prelude += `--${boundary}\r\n`;
      prelude += `Content-Disposition: form-data; name="${k}"\r\n\r\n${String(v)}\r\n`;
    }
    prelude += `--${boundary}\r\n`;
    prelude += `Content-Disposition: form-data; name="file"; filename="${fileName}"\r\n`;
    prelude += `Content-Type: application/octet-stream\r\n\r\n`;

    const epilogue = `\r\n--${boundary}--\r\n`;

    return new Promise<HttpResponse<T>>((resolve, reject) => {
      const req = this.createRequest(
          {
            method: 'POST',
            pathname,
            headers: this.withAuthHeaders({
              'Content-Type': `multipart/form-data; boundary=${boundary}`,
              ...headers,
            }),
          },
          (res) => {
            this.captureSetCookie(res.headers['set-cookie']);
            let chunks = '';
            res.setEncoding('utf8');
            res.on('data', (c) => (chunks += c));
            res.on('end', () => resolve(this.buildResponse<T>(res, chunks)));
          }
      );

      req.on('error', reject);

      req.write(prelude);
      const fileStream = fs.createReadStream(filePath);
      fileStream.on('error', reject);
      fileStream.on('end', () => {
        req.write(epilogue);
        req.end();
      });
      fileStream.pipe(req, { end: false });
    });
  }

  // ---------------- 内部实现 ----------------

  private request<T = unknown>(opt: {
    method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
    pathname: string;
    headers?: H;
    body?: string | Buffer;
  }): Promise<HttpResponse<T>> {
    return new Promise<HttpResponse<T>>((resolve, reject) => {
      const req = this.createRequest(
          {
            method: opt.method,
            pathname: opt.pathname,
            headers: this.withAuthHeaders(opt.headers ?? {}),
          },
          (res) => {
            this.captureSetCookie(res.headers['set-cookie']);
            let chunks = '';
            res.setEncoding('utf8');
            res.on('data', (c) => (chunks += c));
            res.on('end', () => resolve(this.buildResponse<T>(res, chunks)));
          }
      );

      req.on('error', reject);
      if (opt.body !== undefined) req.write(opt.body);
      req.end();
    });
  }

  private createRequest(
      opt: { method: string; pathname: string; headers?: H },
      cb: (res: http.IncomingMessage) => void
  ) {
    const common = {
      hostname: this.hostname,
      port: this.port,
      path: opt.pathname,
      method: opt.method,
      headers: opt.headers,
    };
    return this.protocol === 'https:'
        ? https.request(common as https.RequestOptions, cb)
        : http.request(common as http.RequestOptions, cb);
  }

  /** 把 token + Cookie 自动加到请求头里 */
  private withAuthHeaders(headers: H): H {
    const h: H = { ...headers };
    if (this.token) {
      // 注意：部分后端要求大写 X-CSRF-TOKEN；如需改名就在这里改
      h['x-csrf-token'] = String(this.token);
    }
    const cookie = this.cookieHeader();
    if (cookie) {
      h['Cookie'] = cookie;
    }
    // 可选：有些后端校验来源
    if (!('Origin' in h)) h['Origin'] = `${this.protocol}//${this.hostname}:${this.port}`;
    if (!('Referer' in h)) h['Referer'] = `${this.protocol}//${this.hostname}:${this.port}/`;
    return h;
  }

  /** 把 Set-Cookie 合并进本地 cookieJar */
  private captureSetCookie(setCookie: string[] | undefined) {
    if (!setCookie || !setCookie.length) return;
    for (const line of setCookie) {
      // 只取 "name=value"；忽略 Path/Expires/HttpOnly 等属性
      const [nv] = line.split(';');
      const eqIdx = nv.indexOf('=');
      if (eqIdx > 0) {
        const name = nv.slice(0, eqIdx).trim();
        const value = nv.slice(eqIdx + 1).trim();
        this.cookieJar[name] = value; // 覆盖同名 cookie
      }
    }
  }

  /** 组装 Cookie 头 */
  private cookieHeader(): string {
    const pairs = Object.entries(this.cookieJar).map(([k, v]) => `${k}=${v}`);
    return pairs.length ? pairs.join('; ') : '';
  }

  private buildResponse<T>(res: http.IncomingMessage, body: string): HttpResponse<T> {
    let json: T | undefined;
    try { json = JSON.parse(body) as T; } catch {}
    return { statusCode: res.statusCode, headers: res.headers, body, json };
  }

  public applyAuthHeaders(headers: Record<string, string | number>) {
    return this.withAuthHeaders(headers);
  }

}
